package cn.szsys.integration.billrule.aspect;

import cn.hutool.core.collection.CollUtil;
import cn.szsys.integration.billrule.BillingMapperUtils;
import cn.szsys.integration.billrule.annoation.BillRule;
import cn.szsys.integration.billrule.entity.BillingInfo;
import cn.szsys.integration.billrule.entity.BillingRuleData;
import cn.szsys.integration.billrule.strtegy.impl.DayBillRuleDelaySaveRecord;
import cn.szsys.integration.constants.AppConstant;
import cn.szsys.integration.constants.BillRuleRecordTypeEnum;
import cn.szsys.integration.constants.BillingRuleServiceTypeEnum;
import cn.szsys.integration.constants.BillingRuleTypeEnum;
import cn.szsys.integration.dos.BillingRuleDO;
import cn.szsys.integration.ex.BusinessException;
import cn.szsys.integration.repository.BillingRuleRepository;
import cn.szsys.integration.util.R;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author Ryan
 * @since 2025-05-06
 **/
@Component
@Aspect
@Slf4j
public class BillRuleAspect {

    static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");


    @Resource
    private BillingRuleRepository billingRuleRepository;
    @Resource
    private DayBillRuleDelaySaveRecord dayBillRuleDelaySaveRecord;

    /**
     * 不能在多线程中处理。 否则会抛出NullPointerException
     *
     * @param point    point
     * @param billRule billRule
     * @return {@link Object}
     * @author Ryan
     */
    @Around("@annotation(billRule)")
    public Object process(ProceedingJoinPoint point, BillRule billRule) throws Exception {
        log.info("计算获取请求计费");
        long startTime = System.currentTimeMillis();
        //当天日期格式化字符串
        String theDay = DATE_TIME_FORMATTER.format(LocalDateTime.now());

        Object proceed;
        //下游名称缩写
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String downstream = request.getHeader(AppConstant.DOWNSTREAM_ABBREVIATION);

        Map<Integer, List<BillingRuleDO>> groupBillingRule = getGroupBillingRule(billRule, downstream);
        //增加下游请求次数
        addDownstreamRequest(billRule, downstream, theDay);
        try {
            proceed = point.proceed();
            //判断是否为结果并且返回成功
            if (proceed instanceof R<?> r && r.isSuccess()) {
                boolean downBilled = billingRuleReferee(groupBillingRule, billRule, downstream, theDay, startTime);
                r.setBilled(downBilled);
            }
        } catch (Throwable e) {
            log.error("执行计费相关的请求异常,出错了[{}]", e.getMessage(), e);
            //有计费数据也增加记录
            billingRuleReferee(groupBillingRule, billRule, downstream, theDay, startTime);
            throw (Exception) e;
        } finally {
            BillingMapperUtils.remove();
        }
        return proceed;
    }

    /**
     * 获取上下游计费规则
     *
     * @param billRule         billRule
     * @param downAbbreviation downAbbreviation
     * @return {@link Map<Integer,List<BillingRuleDO>>}
     * @author Ryan
     */
    private Map<Integer, List<BillingRuleDO>> getGroupBillingRule(BillRule billRule, String downAbbreviation) throws Exception {
        //上游名称缩写
        String upstream = billRule.upstream();
        //查询计费规则
        LambdaQueryWrapper<BillingRuleDO> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(BillingRuleDO::getAbbreviation, upstream)
                .eq(BillingRuleDO::getServiceType, BillingRuleServiceTypeEnum.UPSTREAM.getCode())
                .eq(BillingRuleDO::getProduct, billRule.product())
                .or()
                .eq(BillingRuleDO::getAbbreviation, downAbbreviation)
                .eq(BillingRuleDO::getServiceType, BillingRuleServiceTypeEnum.DOWNSTREAM.getCode())
                .eq(BillingRuleDO::getProduct, billRule.product());
        List<BillingRuleDO> list = billingRuleRepository.list(wrapper);
        if (CollUtil.isEmpty(list)) {
            throw new BusinessException("未查询到上下游计费规则");
        }
        Map<Integer, List<BillingRuleDO>> groupBillingRule = billingRuleRepository.list(wrapper)
                .stream()
                .collect(Collectors.groupingBy(BillingRuleDO::getServiceType));
        if (CollUtil.isEmpty(groupBillingRule)) {
            throw new BusinessException("未配置上游和下游计费规则");
        }
        if (!groupBillingRule.containsKey(BillingRuleServiceTypeEnum.UPSTREAM.getCode())) {
            throw new BusinessException("未配置上游计费规则");
        }
        if (!groupBillingRule.containsKey(BillingRuleServiceTypeEnum.DOWNSTREAM.getCode())) {
            throw new BusinessException("未配置下游计费规则");
        }
        return groupBillingRule;

    }

    /**
     * 增加下游请求次数
     * @author Ryan
     * @param billRule billRule
     * @param downstream downstream
     * @param theDay theDay
     */
    private void addDownstreamRequest(BillRule billRule, String downstream, String theDay) {
        //按日统计
        if (Objects.equals(BillRuleRecordTypeEnum.DAY, billRule.billRuleRecordType())) {
            BillingRuleData billingRuleData = BillingRuleData.builder()
                    .theDay(theDay)
                    .product(billRule.product())
                    .upAbbreviation(billRule.upstream())
                    .downAbbreviation(downstream)
                    .upBilled(false)
                    .downBilled(false)
                    .requestUpstream(false)
                    .requestDownstream(true)
                    .build();
            dayBillRuleDelaySaveRecord.addRecord(billingRuleData);
        }
        //按每次请求单独统计
        if (Objects.equals(BillRuleRecordTypeEnum.SINGLE, billRule.billRuleRecordType())) {
            //todo 后期实现
        }
    }

    /**
     * @param groupBillingRule groupBillingRule
     * @param billRule         billRule
     * @param downstream       downstream
     * @param theDay           theDay
     * @param startTime        startTime
     * @author Ryan
     */
    private boolean billingRuleReferee(Map<Integer, List<BillingRuleDO>> groupBillingRule
            , BillRule billRule, String downstream, String theDay, long startTime) {
        BillingInfo billingInfo = BillingMapperUtils.get();
        //存在计费记录才保存(能获取到计费信息就增加记录)
        if (billingInfo == null) {
            return false;
        }
        boolean upFlag = false;
        boolean downFlag = false;
        for (Integer serviceType : groupBillingRule.keySet()) {
            List<BillingRuleDO> billingRules = groupBillingRule.get(serviceType);
            if (serviceType == BillingRuleServiceTypeEnum.UPSTREAM.getCode()) {
                upFlag = billingRuleRefereeLoop(billingRules, billingInfo, startTime);
            }
            if (serviceType == BillingRuleServiceTypeEnum.DOWNSTREAM.getCode()) {
                downFlag = billingRuleRefereeLoop(billingRules, billingInfo, startTime);
            }
        }
        //按日统计
        if (Objects.equals(BillRuleRecordTypeEnum.DAY, billRule.billRuleRecordType())) {
            BillingRuleData billingRuleData = BillingRuleData.builder()
                    .theDay(theDay)
                    .product(billRule.product())
                    .upAbbreviation(billRule.upstream())
                    .downAbbreviation(downstream)
                    .upBilled(upFlag)
                    .downBilled(downFlag)
                    .requestUpstream(true)
                    .requestDownstream(false)
                    .build();
            dayBillRuleDelaySaveRecord.addRecord(billingRuleData);
        }
        //按每次请求单独统计
        if (Objects.equals(BillRuleRecordTypeEnum.SINGLE, billRule.billRuleRecordType())) {
            //todo 后期实现
        }
        return downFlag;
    }


    /**
     * 遍历所有上/下游计费规则,都满足时返回true,否则返回false
     *
     * @param billingRules
     * @param billingInfo
     * @param startTime
     * @return {@link boolean}
     * @author Ryan
     */
    @SuppressWarnings("all")
    private boolean billingRuleRefereeLoop(List<BillingRuleDO> billingRules, BillingInfo billingInfo, long startTime) {
        boolean flag = false;
        for (BillingRuleDO billingRuleDO : billingRules) {
            BillingRuleTypeEnum billingRuleType = BillingRuleTypeEnum.getByCode(billingRuleDO.getType());
            billingInfo.setDownResponseTime(System.currentTimeMillis() - startTime);
            flag = billingRuleType.isBilling(billingInfo, billingRuleDO);
            log.info("计费规则,上/下游数据:[{}], 规则数据:[{}], 计算结果:[{}]", billingInfo, billingRuleDO, flag);
        }
        return flag;
    }
}
